\input texinfo   @c -*-texinfo-*-
@c %**start of header
@setfilename rt-liber.info
@settitle The rt-liberation Manual
@c %**end of header

@c History: This manual was started on the 6th of April 2009. Yoni
@c Rabkin (yrk@gnu.org) is the primary author.

@dircategory Emacs
@direntry
* rt-liberation: (rt-liber).    Emacs Interface to RT
@end direntry


@copying
 @copyright{} 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2020  Free Software Foundation, Inc.
   
@quotation
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.2 or
any later version published by the Free Software Foundation; with no
Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A
copy of the license is included in the section entitled ``GNU Free
Documentation License''.
@end quotation
@end copying


@c For printed material
@titlepage
@title The rt-liberation Manual
@page
@vskip 0pt plus 1filll
@insertcopying
@end titlepage
@contents
@c END For printed material


@ifnottex
@node Top, Introduction, (dir), (dir)
@top The rt-liberation Manual

This is the Manual for the rt-liberation system

@insertcopying

@menu
* Introduction::                Introduction to rt-liberation.
* Installation::                Install rt-liberation on the system.
* Configuration::               Setup rt-liberation to work on the system.

Using rt-liberation
* Queries::                     Retrieve particular tickets from the server.
* Ticket Browser::              Browse the query results.
* Ticket Viewer::               Interface to query results.

Extensions
* Gnus Integration::            Sending email to the RT server via Gnus.
* Tracking Updates::            Keeping up to date with ticket changes.
* Batch Operations::            Performing operations on batches of tickets.
* Local Storage::               Associate arbitrary data with tickets.

Copying and license
* Copying::                     The GNU General Public License gives you
                                permission to redistribute rt-liberation
                                on certain terms; it also explains that
                                there is no warranty.

* The GNU FDL::                 The license for this documentation.

Indices
* Concept Index::
* Function Index::
* Variable Index::
* Keybinding Index::

@detailmenu
 --- The Detailed Node Listing ---

Queries

* Query Compiler::              Compiling Emacs Lisp to TicketSQL.
* Query Language::              A description of the Sexp-based language.

Ticket Browser

* Ticket Browser Display::      How tickets are displayed in the browser.
* Ticket Browser Sorting::      How tickets are sorted in the browser.
* Ticket Browser Filtering::    How to filter tickets out of the browser.
* Multiple Ticket Browsers::    More than one ticket browser buffer.

@end detailmenu
@end menu

@end ifnottex


@c --------------------------------------------------
@node Introduction
@chapter Introduction
@cindex introduction

rt-liberation is a GNU/Emacs package for working with the Request
Tracker (henceforth abbreviated as just ``RT'') software from Best
Practical Solutions. RT has an interactive Web interface, a command
line interface (the ``RT CLI''), and a REST interface. rt-liberation
uses the RT REST interface to communicate with the RT server.

rt-liberation allows sending search queries to the RT server, browsing
the resulting tickets, viewing the tickets' contents and performing
operations on the tickets.


@c --------------------------------------------------
@node Installation
@chapter Installation
@cindex installation

rt-liberation is available via GNU ELPA. Invoke @kbd{M-x
list-packages} and choose to install the rt-liberation package.

If you install rt-liberation manually, by copying the code to your
machine, instead you'll need to tell Emacs where to find it, and then
tell Emacs to load the package:

@lisp
(add-to-list 'load-path "/PATH/TO/rt-liberation/")
@end lisp

@lisp
(require 'rt-liberation)
@end lisp


@c --------------------------------------------------
@node Configuration
@chapter Configuration
@cindex configuration

rt-liberation needs to be configured in your ~/.emacs, an ~/.rt-liber
file, or similar.

Tell rt-liberation where to find the RT server's REST interface:

@lisp
(setq rt-liber-rest-url "rt.example.org")
@end lisp

In order to authenticate with the RT server instance you need to
provide credentials. rt-liberation looks for these in the variables
@var{rt-liber-rest-username} and @var{rt-liber-rest-password}. You can
set these directly:

@lisp
(setq rt-liber-rest-username "someuser"
      rt-liber-rest-password "somepassword")
@end lisp

You can also leave these values unset (@code{nil}), in which case
rt-liberation will look for the credentials in a Netrc file via the
auth-source library (see: @xref{Top,,, auth, Emacs auth-source}),
under the machine name "rt-liberation":

@example
machine rt-liberation login someuser password somepassword
@end example

rt-liberation can issue a command to ``take'' a ticket (that is,
assign it to yourself). For this the variable @var{rt-liber-username}
must be set:

@lisp
(setq rt-liber-username "someuser")
@end lisp

rt-liberation can launch a Web browser to visit a ticket. For that to
work the base URL needs to be set in @var{rt-liber-base-url}. For
example:

(setq rt-liber-base-url "https://rt.foo.org/")


@c --------------------------------------------------
@node Queries
@chapter Queries
@cindex queries

A typical RT server is meant to manage a large amount of tickets. Much
more that would be convenient to view all at once. Instead queries are
used to view only a subset of the tickets on the server.

rt-liberation has its own Sexp-based query language which maps to RT's
TicketSQL language.

@menu
* Query Compiler::              Compiling Emacs Lisp to TicketSQL.
* Query Language::              A description of the Sexp-based language.
@end menu


@c --------------------------------------------------
@node Query Compiler
@section Query Compiler
@cindex query compiler

In order to browse and view tickets a list of needs to be requested
from the RT server. Typically the tickets answer some kind of
criteria, for example ``tickets no older than a week owned by me which
have \``foobar\'' in their subject line''. In RT these criteria are
formulated with ``TicketSQL'' queries; a structured query language
specific to RT.

rt-liberation provides a query compiler function to compile Emacs Lisp
symbolic expressions into TicketSQL. The query compiler supports a
number of TicketSQL tokens.


@c --------------------------------------------------
@node Query Language
@section Query Language
@cindex query language

rt-liberation's Sexp-based query language covers a portion of the
TicketSQL language. Here are some of the supported TicketSQL tokens:
Boolean tokens as a means of combining query subsections: ``and'', ``or'',
``not''. LIKE attribute tokens: ``subject'', ``content''.

For example here is a query with both Boolean and LIKE tokens:

@lisp
(rt-liber-compile-query
    (and (queue "bugs")
         (content "gnu")))

==> "Queue = 'bugs' AND Content LIKE 'gnu'"
@end lisp

We can also express negation (note that the compiler produces "!=" and
"NOT LIKE" for negation depending on the context):

@lisp
(rt-liber-compile-query
 (and (queue "bugs")
      (not (owner "Nobody"))
      (not (content "sprigz"))
      (status "new")))

==> "Queue = 'licensing' AND Owner != 'Nobody' \
    AND Content NOT LIKE 'sprigz' AND Status = 'new'"
@end lisp

Attribute tokens which match an attribute to a specific field such as:
``owner'', ``status'' and ``queue''. Temporal tokens which limit the search
results to tickets within a certain time interval: ``created'' and
``lastupdated''. Note that temporal keywords such as ``created'' always
accept two arguments: BEFORE and AFTER. When either BEFORE or AFTER
aren't needed, use NIL instead.

One of the advantages of being able to express the TicketSQL queries
as Emacs Lisp is to be able to express queries using Emacs Lisp
functions.

Here is a slightly more involved example to illustrate:

@lisp
(rt-liber-compile-query
 (and (queue "bugs")
      (owner "me@@myaddress.com")
      (status "open")
      (lastupdated nil
                   (format-time-string
                     "%Y-%m-%d"
                     (seconds-to-time
                      (- (time-to-seconds (current-time))
                         (* 60 60 24 7)))))))

==> "Queue = 'bugs' AND Owner = 'me@@myaddress.com' AND Status = 'open' AND LastUpdated > '2009-03-30'"
@end lisp


Here is an example of how the ticket browser and compiler can be used
in function calls:

@lisp
(defun rt-liber-display-ticket (ticket-id)
  "Display ticket with TICKET-ID in the ticket-browser."
  (interactive "MTicket ID: ")
  (rt-liber-browse-query
   (rt-liber-compile-query
    (and (queue "complaints")
         (id ticket-id)))))
@end lisp


@c --------------------------------------------------
@node Ticket Browser
@chapter Ticket Browser
@cindex ticket browser

The ticket browser is a special buffer which provides a convenient
interface to the results of a server query. The ticket browser can be
started by invoking: (rt-liber-browse-query QUERY), where QUERY is a
TicketSQL query. The TicketSQL query can be entered manually as a
string or as the return value of the query compiler.

@deffn Function rt-liber-browse-query QUERY &optional NEW
Runs QUERY against the server and launches the browser.

If NEW is non-nil then the query results will be displayed in a new
buffer, otherwise the query results will override the contents of the
existing ticket browser buffer. If NEW is a string then that will be
the name of the new buffer.
@end deffn

The TicketSQL query can be the return value of the query compiler. For
example:

@lisp
(rt-liber-browse-query
 (rt-liber-compile-query
     (and (queue "bugs")
          (content "gnu")))
@end lisp


Since the return value of the query compiler is just a TicketSQL
string, the following is equivalent:

@lisp
(rt-liber-browse-query "Queue = 'bugs' AND Content LIKE 'gnu'")
@end lisp

The ticket browser defines a number of commands:

@table @kbd
@item q
@kindex q (ticket browser)
@findex rt-liber-browser-mode-quit
Bury the ticket browser buffer.

@item n
@kindex n (ticket browser)
@findex rt-liber-next-ticket-in-browser
Move point to the next ticket.

@item p
@kindex p (ticket browser)
@findex rt-liber-previous-ticket-in-browser
Move point to the previous ticket.

@item RET
@kindex RET (ticket browser)
@findex rt-liber-display-ticket-at-point
Visit the ticket at point in the @xref{Ticket Viewer}.

@item g
@kindex g (ticket browser)
@findex revert-buffer
Refresh the contents of the browser buffer.

@item G
@kindex G (ticket browser)
@findex rt-liber-browser-refresh-and-return
Refresh the contents of the browser buffer. Return point to the
current ticket after the refresh (if possible).

@item s
@kindex s (ticket browser)
@findex rt-liber-browser-mark-as-spam
Mark the ticket as spam.

@item S
@kindex S (ticket browser)
@findex rt-liber-multi-delete-spam
Delete marked tickets as spam (requires rt-liberation-multi package).

@item a
@kindex a (ticket browser)
@findex rt-liber-browser-assign
Assign the ticket to a user.

@item r
@kindex r (ticket browser)
@findex rt-liber-browser-resolve
Mark the ticket as ``resolved''.

@item o
@kindex o (ticket browser)
@findex rt-liber-browser-open
Mark the ticket as ``open''.

@item t
@kindex t (ticket browser)
@findex rt-liber-browser-take-ticket-at-point
Assign the ticket at point to @var{rt-liber-username}.

@item SPC
@kindex SPC (ticket browser)
@findex scroll-up
Scroll the text of the ticket browser upward.

@item DEL
@kindex DEL (ticket browser)
@findex scroll-down
Scroll the text of the ticket browser downward.

@item m
@kindex m (ticket browser)
@findex rt-liber-browser-move
Move the ticket to a different queue.

@item P
@kindex P (ticket browser)
@findex rt-liber-browser-prioritize
Set the numerical priority level of the ticket at point.

@end table

@menu
* Ticket Browser Display::      How tickets are displayed in the browser.
* Ticket Browser Sorting::      How tickets are sorted in the browser.
* Ticket Browser Filtering::    How to filter tickets out of the browser.
* Multiple Ticket Browsers::    More than one ticket browser buffer.
@end menu


@c --------------------------------------------------
@node Ticket Browser Display
@section Ticket Browser Display
@cindex ticket browser display function

The ticket browser displays the tickets in the browser by calling
@dfn{rt-liber-ticketlist-browser-redraw-f} which can be changed and
customized. Any implementation of
@dfn{rt-liber-ticketlist-browser-redraw-f} must leave point at the end
of the ticket text.

The ticket data itself can be displayed using rt-liberation ticket
format string %-sequences:

@table @asis
@item %i
ID number of the ticket in the RT database.
@item %s
Subject line.
@item %c
Ticket creation time. The format to display the time is specified in
the variable @var{rt-liber-browser-time-format-string}.
@item %S
Ticket status (``open'', ``new'' etc.)
@item %r
Whether the ticket is resolved.
@item %R
Requestor/s
@item %C
Creator of the ticket.
@item %o
Owner of the ticket.
@item %q
The queue originating the ticket.
@item %p
The numerical priority of the ticket
@end table

Here is an example implementation of
@dfn{rt-liber-ticketlist-browser-redraw-f} showing the use of the
%-sequences. Note the use of text properties to add color to ticket
text. The use of text properties as opposed to font-locking is meant
to ease customization because otherwise any change in ticket display
would break the font-locking regular expressions.

@lisp
(defun rt-liber-ticketlist-browser-redraw-f (ticket)
  "Display TICKET."
  (insert (rt-liber-format "[%c] %i" ticket))
  (add-text-properties (point-at-bol)
                       (point-at-eol)
                       '(face rt-liber-ticket-face))
  (newline)
  (insert (rt-liber-format "    [%S] %s" ticket))
  (newline)
  (insert (rt-liber-format "    %o <== %R" ticket)))
@end lisp

The function @dfn{rt-liber-high-priority-p} can be used to apply a
different face or text to a ticket if it is high priority. A ticket is
considered high priority if its value is strictly higher than
@var{rt-liber-browser-priority-cutoff}


@c --------------------------------------------------
@node Ticket Browser Sorting
@section Ticket Browser Sorting
@cindex ticket browser sorting

The tickets in the browser are displayed by default in reverse
chronological order. Ticket sorting is done by a call to
@dfn{rt-liber-browser-default-sorting-function}.

Other sorting orders can be used by binding
@dfn{rt-liber-browser-default-sorting-function} to a different
function. To ease writing such functions rt-liberation provides two
predicate functions to perform comparisons between ticket objects:

@defun rt-liber-lex-lessthan-p a b field
Return true if A is lexicographically less than B in FIELD.

Here is an example of sorting tickets lexicographically by owner name
using @dfn{rt-liber-lex-lessthan-p} (note that you can feed
@dfn{rt-liber-lex-lessthan-p} a date/time string and it will sort it
just fine except that it wouldn't make any sense):

@lisp
(defun rt-liber-sort-by-owner (ticket-list)
  "Sort TICKET-LIST lexicographically by owner."
  (rt-liber-sort-ticket-list
   ticket-list
   #'(lambda (a b)
       (rt-liber-lex-lessthan-p a b "Owner"))))
@end lisp

@end defun

@defun rt-liber-time-lessthan-p a b field
Return t if A is chronologically less than B in FIELD.

Here is an example of sorting tickets lexicographically by owner name
using @dfn{rt-liber-time-lessthan-p} (note that feeding
@dfn{rt-liber-time-lessthan-p} anything but a date/time string, in
this case ``Created'' contains a date, will result in an error being
signaled).

@lisp
(defun rt-liber-sort-by-time-created (ticket-list)
  "Sort TICKET-LIST in reverse chronological order."
  (reverse
   (rt-liber-sort-ticket-list
    ticket-list
    #'(lambda (a b)
        (rt-liber-time-lessthan-p a b "Created")))))
@end lisp

@end defun


@c -------------------------------------------------------------------
@node Ticket Browser Filtering
@section Ticket Browser Filtering
@cindex ticket browser filtering filter

The Ticket Browser can also filter out (that is, not display) certain
tickets based on particular criteria. This probably shouldn't be used
instead of a properly formed RT query, but when used in conjunction
with correctly formulated queries it becomes a powerful tool.

During ticket display processing the Ticket Browser will call the
function pointed to by @var{rt-liber-browser-default-filter-function}
on each ticket, passing the function the ticket alist as a single
argument. The function is set by default to
@dfn{rt-liber-default-filter-f}, which is a function which will
display all tickets and filter none.

If any tickets are filtered, the Ticket Browser will display the
filtered ticket count at the bottom ticket listing.

Here is a simple example of how to filter out all of the tickets which
have a status of ``deleted''.

First we define a custom filter function. Note how it accepts a single
argument, which is the ticket alist, and returns nil if the ticket is
to be filtered.

@lisp
(defun rt-liber-browser-deleted-filter (ticket)
  (not
   (and ticket
        (string= (cdr (assoc "Status" ticket))
                 "deleted"))))
@end lisp

Then we assign that function to be our default filtering function:

@lisp
(setq rt-liber-browser-default-filter-function
      'rt-liber-browser-deleted-filter)
@end lisp


@c -------------------------------------------------------------------
@node Multiple Ticket Browsers
@section Multiple Ticket Browsers
@cindex ticket browser multiple buffer

It is sometimes useful to rename the ticket browser buffer to
something more informative than the default
@var{rt-liber-browser-buffer-name}, especially if there are multiple
ticket browsers.

Changing a ticket browser's name can be done normally with
`rename-buffer', but it is also possible to name the ticket browser
when it is created. In the following example two ticket browser
buffers will be created displaying the query results and named
``*updated by supervisor*'' and ``*new tickets*'' respectively:

@lisp
(defun rt-liber-daily-rounds ()
  (interactive)

  (rt-liber-browse-query
   (rt-liber-compile-query
    (and (queue "complaints")
         (owner "lem.e.tweakit")
         (status "open")
         (lastupdatedby "molly.manager")))
   "*updated by supervisor*")

  (rt-liber-browse-query
   (rt-liber-compile-query
    (and (queue "complaints")
         (owner "Nobody")
         (status "new")))
   "*new tickets*"))
@end lisp


@c --------------------------------------------------
@node Ticket Viewer
@chapter Ticket Viewer
@cindex ticket viewer

The ticket viewer is an interface for viewing the contents of a ticket
and for sending answers.

The ticket viewer provides key-bindings to help compose emails to send
to the RT email interface. The key-bindings for composing email
described below are generic, what actually happens when you invoke
them depends on the email-backend system you have installed into
rt-liberation. @file{rt-liberation-gnus.el} provides integration with
Gnus, @xref{Gnus Integration}.

@table @kbd

@item q
@kindex q (ticket viewer)
@findex rt-liber-viewer2-mode-quit
Bury the ticket viewer buffer.

@item n
@kindex n (ticket viewer)
@findex rt-liber-viewer2-next-section-in
Move to the next section in ticket.

@item N
@kindex N (ticket viewer)
@findex rt-liber-viewer2-last-section-in
Move to the last section.

@item p
@kindex p (ticket viewer)
@findex rt-liber-viewer2-previous-section-in
Move point to the previous section in ticket.

@item V
@kindex V (ticket viewer)
@findex rt-liber-viewer-visit-in-browser
Visit the current ticket in a Web browser.

@item M
@kindex M (ticket viewer)
@findex rt-liber-viewer2-answer
Compose an answer to the current ticket. The content section around
point will be inserted into the email body and commented out.

@item C
@kindex C (ticket viewer)
@findex rt-liber-viewer2-comment
Comment on the ticket using the current context

@item g
@kindex g (ticket viewer)
@findex revert-buffer
Refresh and redisplay the current ticket.

@item SPC
@kindex SPC (ticket viewer)
@findex scroll-up
Scroll text of ticket viewer upward.

@item DEL
@kindex DEL (ticket viewer)
@findex scroll-down
Scroll text of ticket viewer downward.

@item h
@kindex h (ticket viewer)
@findex rt-liber-viewer-show-ticket-browser
Display the associated ticket in the ticket browser.

@end table


@c --------------------------------------------------
@node Gnus Integration
@chapter Gnus Integration
@cindex Gnus Integration

The file @file{rt-liberation-gnus.el} implements integration with Gnus
for composing emails. To enable the feature, `require' it after
loading rt-liberation:

@lisp
(require 'rt-liberation-gnus)
@end lisp

In order for rt-liberation-gnus to be useful a few variables need to
be specialized. The following is example code which sets these
variables. Below is a thorough description of those variables.

@lisp
(setq rt-liber-gnus-comment-address "our-rtserver-comment@@ourserver.org"
      rt-liber-gnus-address         "our-rtserver@@ourserver.org"
      rt-liber-gnus-subject-name    "ourserver.org")
@end lisp

@defopt rt-liber-gnus-address
@var{rt-liber-gnus-address} is the email address which is configured
in the RT server email interface for sending a response to the
ticket's requestor.
@end defopt
@defopt rt-liber-gnus-comment-address
@var{rt-liber-gnus-comment-address} is the email address which is
configured in the RT server email interface for adding a comment under
the ticket in question.
@end defopt
@defopt rt-liber-gnus-subject-name
@var{rt-liber-gnus-subject-name} is a string, typically included at
the beginning of the square brackets in the subject. The string is a
part of the subject line which helps the RT server recognize the
email.
@end defopt

Gnus posting styles controlled by @var{gnus-posting-styles} can be
customized for rt-liberation-gnus by using the variable
@var{rt-liber-gnus-p}, which is only non-nil when rt-liberation-gnus
launches a Gnus message buffer.

Here is example code which uses @var{rt-liber-gnus-p} to override the
signature in the default posting style with one special to
rt-liberation. Headers can be added and removed in a similar manner.

@lisp
(setq gnus-posting-styles
        '((".*"
           (name "Lemm E. Hackitt")
           (address "Lemm@@hack.it")
           (signature-file "~/sig.txt")
           ("X-Ethics" "Use GNU"))
          (rt-liber-gnus-p
           (signature-file "~/rt-liber-sig.txt"))))
@end lisp


Once rt-liberation-gnus is loaded and customized the key-bindings in
the Viewer will be able to call into it, @xref{Ticket Viewer}.


@c --------------------------------------------------
@node Tracking Updates
@chapter Tracking Updates
@cindex Tracking Updates

The functions in @file{rt-liberation-update.el} help keep up with
updates to the ticket database. To enable the feature, `require' it
after loading rt-liberation:

@lisp
(require 'rt-liberation-update)
@end lisp

Then set @var{rt-liber-update-default-queue} to be the name of the
queue to watch for updates. For example:

@lisp
(setq rt-liber-update-default-queue "complaints")
@end lisp

@defun rt-liber-update &optional no-update
@code{rt-liber-update} is an interactive function which runs a query
against the RT server asking for the tickets which have been updated
since the time @code{rt-liber-update} was last run (each time it runs,
it leaves a time-stamp). If no time-stamp is found, for instance when
you run @code{rt-liber-update} for the first time, today's date is
used.

With the NO-UPDATE prefix, @code{rt-liber-update} will not update the
time-stamp so that the next invocation will produce the same result.
@end defun


@c --------------------------------------------------
@node Batch Operations
@chapter Batch Operations
@cindex Batch Operations

The extension @file{rt-liberation-multi.el} implements performing
batch operations on groups of tickets. It works in two stages: First
mark an arbitrary number of tickets within the same buffer then call a
batch operation function on them. The batch operation functions work
the same way as function which work on single tickets only that they
iterate through all of the marked tickets.

To enable batch operations first load @file{rt-liberation-multi.el}:

@lisp
(require 'rt-liberation-storage)
@end lisp

@table @kbd
@item M
@kindex M (ticket browser)
@findex rt-liber-mark-ticket-at-point
Mark the ticket at point for future action. If the ticket at point is
already marked then unmark it.
@end table

@defun rt-liber-multi-set-status-open
Set the status of all the marked tickets to ``open''.
@end defun

@defun rt-liber-multi-set-status-resolved
Set the status of all the marked tickets to ``resolved.
@end defun

@defun rt-liber-multi-assign name
Assign all of the marked tickets to NAME.
@end defun

@defun rt-liber-multi-flag-as-spam-and-delete
Set the status of all the marked tickets to ``is-spam'' and delete.
@end defun


@c --------------------------------------------------
@node Local Storage
@chapter Local Storage
@cindex Local Storage

@file{rt-liberation-storage.el} implements associating arbitrary
ancillary data with tickets. The data is stored locally and is not
sent to the RT server.

To enable local storage first load @file{rt-liberation-storage.el}:

@lisp
(require 'rt-liberation-storage)
@end lisp

Then enable the display of ancillary data with:

@lisp
(setq rt-liber-anc-p t)
@end lisp

The associated data is edited and displayed in the ticket browser with
the following command key:

@table @kbd
@item A
@kindex A (ticket browser)
@findex rt-liber-browser-ancillary-text
Associate text with the ticket at point. You will be prompted to enter
a string of text.
@end table

Once text is associated with a ticket it will be displayed alongside
that ticket in the ticket browser. This particular feature lends
itself to creating private annotations about tickets.

The implementation distributed with rt-liberation allows associating
text with tickets but is not limited to text. The same implementation
can be extended to associate any arbitrary data with any ticket.


@c --------------------------------------------------
@c including the relevant licenses
@include gpl.texi
@include fdl.texi


@c --------------------------------------------------
@node Concept Index
@unnumbered Concept Index
@printindex cp


@c --------------------------------------------------
@node Function Index
@unnumbered Function Index
@printindex fn


@c --------------------------------------------------
@node Variable Index
@unnumbered Variable Index
@printindex vr


@c --------------------------------------------------
@node Keybinding Index
@unnumbered Keybinding Index
@printindex ky


@bye
